/*
* JBoss, Home of Professional Open Source
* Copyright 2015, Red Hat, Inc. and/or its affiliates, and individual
* contributors by the @authors tag. See the copyright.txt in the
* distribution for a full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.as.quickstarts.websocket.client;
import static java.lang.String.format;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Initialized;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.websocket.CloseReason;
import javax.websocket.DeploymentException;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
/**
* @author <a href="http://monospacesoftware.com">Paul Cowan</a>
*/
@ApplicationScoped
public class Frontend extends Endpoint {
private Logger log = Logger.getLogger(Frontend.class.getName());
public static final String WEBSOCKET_PATH = "/relay";
private SessionManager sessionManager = new SessionManager();
@Inject
@ToBackend
private Event<SessionMessage> backendTrigger;
public void init(@Observes @Initialized(ApplicationScoped.class) ServletContext servletContext) {
if (servletContext == null) {
log.severe(format("Failed to deploy frontend endpoint %s: %s", WEBSOCKET_PATH, "ServletContext not available"));
return;
}
ServerContainer serverContainer = (ServerContainer) servletContext.getAttribute("javax.websocket.server.ServerContainer");
if (serverContainer == null) {
log.severe(format("Failed to deploy frontend endpoint %s: %s", WEBSOCKET_PATH, "javax.websocket.server.ServerContainer ServerContainer not available"));
return;
}
// WebSocket does not honor CDI contexts in the default configuration; by default a new object is created for each new
// websocket.
// We can work around this by supplying a custom ServerEndpointConfig.Configurator that returns the same instance of the
// endpoint for each new websocket. Unfortunately this precludes us from using annotations, and forces us to extend the
// abstract class Endpoint, and forces us to add the server endpoint programmatically upon startup of the servlet
// context.
ServerEndpointConfig config = ServerEndpointConfig.Builder
.create(Frontend.class, WEBSOCKET_PATH)
.configurator(new ServerEndpointConfig.Configurator() {
@SuppressWarnings("unchecked")
@Override
public <T> T getEndpointInstance(final Class<T> endpointClass) throws InstantiationException {
if (endpointClass.isAssignableFrom(Frontend.class)) { return (T) Frontend.this; }
return super.getEndpointInstance(endpointClass);
}
})
.build();
try {
serverContainer.addEndpoint(config);
} catch (DeploymentException e) {
log.log(Level.SEVERE, format("Failed to deploy frontend endpoint %s: %s", WEBSOCKET_PATH, e.getMessage()), e);
}
}
@Override
public void onOpen(Session session, EndpointConfig config) {
// Endpoint does not handle onMessage; messages are handled by a subclass of MessageHandler.
// Unfortunately onMessage does not pass the Session as a parameter (although it can be injected when using
// annotations).
// Therefore we do not know which session the message came from, which precludes us from using a single instance
// of MessageHandler to handle all session.
session.addMessageHandler(String.class, new FrontendMessageHandler(session));
sessionManager.open(session);
sendMessage(session, format("Opened frontend session %s", session.getId()));
}
@Override
public void onClose(Session session, CloseReason closeReason) {
log.info(format("Closed frontend session %s", session.getId()));
sessionManager.close(session.getId());
}
@Override
public void onError(Session session, Throwable thr) {
log.log(Level.WARNING, format("Error from frontend session %s", session.getId()), thr);
}
public void sendMessage(@Observes @ToFrontend SessionMessage message) {
Session session = sessionManager.get(message.getSessionId());
if (session == null) {
log.warning(format("Frontend session %s not found", message.getSessionId()));
return;
}
sendMessage(session, message.getText());
}
public void sendBroadcast(@Observes @ToBroadcast String text) {
log.info(format("BROADCAST: %s", text));
for (Session session : sessionManager.getAll()) {
sendMessage(session, format("BROADCAST: %s", text));
}
}
public void sendMessage(Session session, String text) {
session.getAsyncRemote().sendText(text);
}
protected class FrontendMessageHandler implements MessageHandler.Whole<String> {
private Session session;
protected FrontendMessageHandler(Session session) {
this.session = session;
}
protected Session getSession() {
return session;
}
@Override
public void onMessage(String text) {
backendTrigger.fire(new SessionMessage(session.getId(), text));
}
}
}